#!/usr/bin/python2.6
#
# Copyright 2011 Eric Gavaletz <gavaletz@gmail.com>
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__author__ = 'Eric Gavaletz <gavaletz@gmail.com>'
__credits__ = 'None'
__date__ = '29 April 2011'
__version__ = '0.1'

#TODO Replace equals with str regex
#TODO Store problem lists in hashed binary format
#TODO PyDoc

import sys
from PyQt4 import QtCore, QtGui
import gui
#from gui import Ui_MainWindow

PROBLEM_LIST = "prob.txt"
OVERTIME = 40
MAX_ATTEMPTS = 3
SEED_CAPITAL = 1000
WORK_WEEK = 40

class GameText(object):
    """
    Text based interface for a game.  This should work with most games as long
    as the game exports methods for pulling status, feedback and the user
    prompt.  The game also needs to have a play method that accepts commands as
    strings for input.
    """

    def __init__(self, parent=None):
        """
        The constructor method.  Starts the console with a text based prompt
        that is provided as a static field from the game.  The user input based
        on this prompt is intended to provide the information that the Game
        object needs to get up and going.  
        """
        name = raw_input(Game.welcome)
        self.game = Game(name)
        self.status(self.game.status)
        self.feedback("", self.game.feedback, self.game.prompt)
    
    def play(self):
        """
        This is the main loop that keeps the game going.
        """
        while 1:
            cmd = raw_input(">>>")
            self.exec_cmd(cmd)

    def quit(self):
        """
        Called by submitting the "quit" command.  This method ends the game and
        closes the application.
        """
        print "Goodbye!"
        sys.exit()

    def abort(self):
        """
        Stops the current action sequence in its tracks and returns the game to
        its home state with the standard root prompt.  
        """
        self.game.abort()
        self.status(self.game.status)
        self.feedback("ABORT", self.game.feedback, self.game.prompt)

    def status(self, s):
        """
        Controls how the status returned from the Game is displayed.
        """
        print "--------------------------\n%s\n--------------------------" % s

    def feedback(self, cmd, fb, cp):
        """
        Controls how the feedback and optionaly the command prompt are
        displayed.
        """
        print "%s\n" % fb
        print "%s\n" % cp

    def exec_cmd(self, cmd):
        """
        Takes text as a command from the play method and passes it to the Game.
        As a feature this also allows the user to quit without having to go into
        the game control logic.
        """
        if cmd == "quit" or cmd == "q":
            self.quit()
        else:
            self.game.play(cmd)
            self.status(self.game.status)
            self.feedback(cmd, self.game.feedback, self.game.prompt)
# End of class GameText

class GameWindow(QtGui.QMainWindow):
    """
    GUI based interface for a game.  This should work with most games as long
    as the game exports methods for pulling status, feedback and the user
    prompt.  The game also needs to have a play method that accepts commands as
    strings for input.

    Depends on PyQt-x11-gpl-4.8.3.tar.gz
    http://www.riverbankcomputing.co.uk/software/pyqt/download
    """

    def __init__(self, parent=None):
        """
        The constructor method.  Starts the console with a text based prompt
        that is provided as a static field from the game.  The user input based
        on this prompt is intended to provide the information that the Game
        object needs to get up and going.  

        There is also some state change that enables the various parts of the
        GUI to serve multiple funations.
        """
        QtGui.QWidget.__init__(self, parent)
        self.ui = gui.Ui_MainWindow()
        self.ui.setupUi(self)
        QtCore.QObject.connect(self.ui.submit_button, QtCore.SIGNAL("clicked()"), self.setup)
        QtCore.QObject.connect(self.ui.command_line, QtCore.SIGNAL("returnPressed()"), self.setup)
        QtCore.QObject.connect(self.ui.actionRetire, QtCore.SIGNAL("triggered()"), self.quit)
        self.ui.feedback_box.append(Game.welcome)

    def setup(self):
        """
        Takes care of the housekeeping and initialization for the GUI window.  
        """
        name = self.ui.command_line.text()
        self.ui.command_line.clear()
        self.game = Game(name)
        self.ui.feedback_box.clear()
        self.status(self.game.status)
        self.feedback("", self.game.feedback, self.game.prompt)
        QtCore.QObject.disconnect(self.ui.submit_button, QtCore.SIGNAL("clicked()"), self.setup)
        QtCore.QObject.disconnect(self.ui.command_line, QtCore.SIGNAL("returnPressed()"), self.setup)
        QtCore.QObject.connect(self.ui.submit_button, QtCore.SIGNAL("clicked()"), self.exec_cmd)
        QtCore.QObject.connect(self.ui.command_line, QtCore.SIGNAL("returnPressed()"), self.exec_cmd)
        QtCore.QObject.connect(self.ui.abort_button, QtCore.SIGNAL("clicked()"), self.abort)

    def quit(self):
        """
        Method for binding the retire/quit menu option with sys.exit()
        """
        sys.exit()

    def abort(self):
        """
        Stops the current action sequence in its tracks and returns the game to
        its home state with the standard root prompt.  
        """
        self.game.abort()
        self.status(self.game.status)
        self.feedback("ABORT", self.game.feedback, self.game.prompt)

    def status(self, s):
        """
        Controls how the status returned from the Game is displayed.
        """
        self.ui.status_box.clear()
        self.ui.status_box.append(s)

    def feedback(self, cmd, fb, cp):
        """
        Controls how the feedback and optionaly the command prompt are
        displayed.
        """
        if not self.ui.actionKeep_Feedback.isChecked():
            self.ui.feedback_box.clear()
        self.ui.feedback_box.append("%s\n" % cmd)
        self.ui.feedback_box.append("%s\n" % fb)
        if self.ui.actionShow_Commands.isChecked():
            self.ui.feedback_box.append("%s\n" % cp)
        else:
            self.ui.feedback_box.append("READY:\n")

    def exec_cmd(self):
        """
        Grabs commands as text from the GUI and passes it to the Game.
        As a feature this also allows the user to quit without having to go into
        the game control logic.
        """
        cmd = self.ui.command_line.text()
        if cmd == "quit" or cmd == "q":
            self.quit()
        else:
            self.game.play(cmd)
        self.ui.command_line.clear()
        self.status(self.game.status)
        self.feedback(cmd, self.game.feedback, self.game.prompt)
# End of class GameWindow

class Office(object):
    """
    The office object handles the calculations and record keeping to ensure that
    the company is paying the right amount for the number of employees that it
    has.  
    """
    b = "basement"
    p = "park"
    d = "downtown"
    s = "scraper"
    c = "campus"
    types = [b, p, d, s, c]
    rent = {b:0, p:1000, d:10000, s:100000, c:1000000}
    max_emp = {b:3, p:10, d:30, s:100, c:1000}
    desc = {b:"Parent's Basement", p:"Office Park", d:"Downtown Office",
            s:"High-rise Building", c:"Corperate Campus"}


    def __init__(self, office_kind):
        """
        The constructor that takes a string to represnet the type of office that
        is being made.  It almost always starts out as a basement.  The number
        of employees is started at zero because the containing company will add
        the owner(s).

        Accepted kinds: [park, downtown, scraper, basement, campus]
        """
        self.num_emp = 0
        if office_kind in self.types:
            self.kind = office_kind

    def rent_bill(self):
        """
        Updates the internally heald rent amount and then returns that amount
        for monthly expense calculations.  The only calculation here that is
        uniqe is the calculation of the rent for a campus.  That calculation is
        1 million for every 1000 employees.
        """
        if self.kind == self.c:
            r = ((self.num_emp / self.max_emp[self.c]) + 1) * self.rent[self.c]
            if r < self.rent[self.c]:
                return self.rent[self.c]
            else:
                return r
        else:
            return self.rent[self.kind]

    def move(self, kind):
        """
        Moving changes the kind of office that is held and makes sure that all
        of the currently employeed workers will fit in the new building.  If
        they will not fit then the user is prompted to fire N employees before
        attempting the move again. 
        """
        if kind == self.c:
            self.kind = self.c
        elif kind in self.types:
            if self.num_emp <= self.max_emp[kind]:
                self.kind = kind
            else:
                return "First fire %d employees!" % (self.num_emp - self.max_emp[kind])
        else:
            return "ERROR: not a recognized office type"
        return "Moved into a %s!" % self.kind

    def add_emp(self):
        """
        If kind == campus then you are always allowed to add more people
        otherwise a check is made to make sure you have enough room first.
        """
        if self.full():
            return "ERROR: Move to a bigger office first."
        else:
            self.num_emp += 1

    def rm_emp(self):
        """
        If employees is not zero then an employee is removed.
        """
        if self.num_emp > 0:
            self.num_emp -= 1
        else:
            return "ERROR: Trying to remove an employee that doesn't exist."

    def full(self):
        """
        Booloean ckeck to see if the number of employees is less than the
        maximum number of employees for a particular kind.
        """
        if self.kind == self.c:
            return False
        else:
            return self.num_emp >= self.max_emp[self.kind]

    def __str__(self):
        return self.desc[self.kind]
# End of class Office

class Company(object):
    """
    The company is the primary data structure and keeps track of the finances,
    temporal progress, and basically hold things togeather.  

    There is contention between the Company and the Problem_list for the
    responsibility of dealing with calculating hours and adding them to the
    hours worked.  For now that has been placed in the Problem_list.
    """
    global OVERTIME, SEED_CAPITAL, WORK_WEEK

    def __init__(self, name):
        """
        Constructor that takes the name of the company (arbitrary string) as an
        argument.  This also sets up the employee list and office.
        """
        self.feedback = ""
        self.name = name
        self.capital = SEED_CAPITAL
        self.employee_list = list()
        self.work_week = WORK_WEEK
        self.hours = 0
        self.weeks = 0
        self.months = 0
        self.years = 0
        self.office = Office(Office.b)
        self.hire_employee(Employee(Employee.o))

    def move_office(self, kind):
        """
        Wrapper for the Office move method.
        """
        return self.office.move(kind)

    def hire_employee(self, emp):
        """
        Takes care of the capital expendatures and the paperwork of hiring an
        employee.  The company must have enough capital to pay the upfront
        recruiting fees but it does not enforce that the company will have
        capital to pay the hourly rate.  This method also checks to make sure
        that the office has space.  

        In the future this should be refactored to an Employee_list class
        function.
        """
        if self.capital >= emp.hire_cost:
            if not self.office.full():
                self.capital -= emp.hire_cost
                self.employee_list.append(emp)
                self.office.add_emp()
                if emp.hours > self.work_week:
                    self.work_week = emp.hours
            else:
                return "ERROR: You need to move to a bigger office first."
        else:
            return "ERROR: You need to raise some more capital first."

    def fire_employee(self, kind):
        """
        Takes care of the capital expendatures and the paperwork of firing an
        employee. 

        In the future this should be refactored to an Employee_list class
        function.
        """
        e = None
        for i in range(0, len(self.employee_list)):
            if self.employee_list[i].kind == kind:
                e = self.employee_list.pop(i)
                break
        if not e:
            return -1
        else:
            self.office.rm_emp()
            payout = 0
            if e.hours_worked <= OVERTIME:
                payout += (e.hours_worked * e.rate)
            else:
                payout += (OVERTIME * e.rate + ((e.hours_worked - OVERTIME) * (e.rate * e.overtime)))
            self.capital -= payout
            return payout

    def employees_with_avail_hours(self):
        """
        Returns a list of employee object that have avail hours to be worked.

        In the future this should be refactored to an Employee_list class
        function.
        """
        tmp = list()
        for e in self.employee_list:
            if e.hours_worked < e.hours:
                tmp.append(e)
        return tmp

    def avail_hours(self):
        """
        Returns a tuple with the total hours (reg, overtime) that the company
        has at its disposal.

        In the future this should be refactored to an Employee_list class
        function.
        """
        reg_hours = 0
        ot_hours = 0
        for e in self.employee_list:
            h = e.avail_hours()
            reg_hours += h[0]
            ot_hours += h[1]
        return (reg_hours, ot_hours)

    def subtract_hours(self, n):
        """
        Calculates the fair share of hours for each employee and deducts them
        appropriately.  As a side effect (out of necessity) it also returns the
        maximum number of hours that any one employee worked for this
        transaction.  This value is used to advance the time.

        In the future this should be refactored to an Employee_list class
        function.
        """
        hours = n
        total_max_share = 0
        while int(hours):
            max_share = 0
            tmp_emp_list = self.employees_with_avail_hours()
            fair_share = hours / float(len(tmp_emp_list))
            if max_share < fair_share:
                max_share = fair_share
            for e in tmp_emp_list:
                if fair_share > e.hours - e.hours_worked:
                    hours -= (e.hours - e.hours_worked)
                    e.hours_worked = e.hours
                else:
                    hours -= fair_share
                    e.hours_worked += fair_share
            total_max_share += max_share
        return total_max_share

    def run_expenses(self):
        """
        Decides which expenses need to be ran and runs them.  This also advances
        the time.
        """
        self.feedback = ""
        tmp = self.weekly_expenses()
        self.weeks += 1
        self.hours -= tmp
        if self.weeks >= 4:
            self.monthly_expenses()
            self.months += 1
            self.weeks -= 4

    def weekly_expenses(self):
        """
        Calculates weekly expenses and deducts them from the capital.  The hours
        are returned to the worker's pool of avail hours.
        """
        weekly_payroll = 0
        max_hours_worked = 0
        for e in self.employee_list:
            if e.hours_worked <= OVERTIME:
                weekly_payroll += (e.hours_worked * e.rate)
            else:
                weekly_payroll += (OVERTIME * e.rate + ((e.hours_worked - OVERTIME) * (e.rate * e.overtime)))
            if max_hours_worked < e.hours_worked:
                max_hours_worked = e.hours_worked
            e.hours_worked = 0
        self.capital -= weekly_payroll
        self.feedback += "\nWeekly Expenses Paid: payroll = $%d.00" % weekly_payroll
        return max_hours_worked

    def monthly_expenses(self):
        """
        Calculates the monthly expenses and deducts them from the capital.
        """
        rent = self.office.rent_bill()
        self.capital -= rent
        benifits = 0
        for e in self.employee_list:
            benifits += e.benifits
        self.capital -= benifits
        self.feedback +=  "\nMonthly Expenses Paid: rent = $%d.00 and monthly employee benifits = $%d.00" % (rent, benifits)

    def get_feedback(self):
        """
        Temporary workaround for incompatibility in feedback mechanisms.  This
        ensures that the feedback (which accumulates in this class) is reset
        when it is read.
        """
        t = self.feedback
        self.feedback = None
        return t

    def __str__(self):
        """
        Returns a string representing the current snapshot of the company's
        resources.
        """
        ret = list()
        ret.append("%s has:" % self.name)
        ret.append("capital = $%d.00" % self.capital)
        ret.append("there are %d employees in your %s" % (len(self.employee_list), self.office.kind))
        ret.append("we are %d hours into week %d of month %d""" % (self.hours, self.weeks, self.months))
        ret.append("---------------------------------")
        ret.append("| Kind\t| Reg.\t| O.T.\t|")
        ret.append("---------------------------------")
        for e in self.employee_list:
            ret.append("| %s \t| %d\t| %s\t|" % (e.kind, e.avail_hours()[0], e.avail_hours()[1]))
        ret.append("---------------------------------")
        ret.append("| Total\t| %d\t| %s\t|" % (self.avail_hours()[0], self.avail_hours()[1]))
        ret.append("---------------------------------")
        return "\n".join(ret)
# End of class Company

class Employee(object):
    """
    This serves as the data structure for all of our employees.  Instead of
    having lots of subclasses for different types of employees this method
    instead takes a configuration kind that will determine the other values.
    """
    global OVERTIME

    i = "intern"
    h = "hourly"
    s = "salary"
    c = "contract"
    o = "owner"
    types = [i, h, s, c, o]
    hc = {i:1000, h:500, s:1000, c:1000, o:0}
    hrs = {i:60, h:50, s:80, c:80, o:50}
    rt = {i:35, h:40, s:50, c:80, o:35}
    ot = {i:1.0, h:1.5, s:0.0, c:1.0, o:1.0}
    ben = {i:0, h:100, s:500, c:0, o:0}


    def __init__(self, emp_kind):
        """
        The default constructor that checks to make sure that the employee type
        that is being requested is a vaild one and constructs that employee.  
        """
        if emp_kind in self.types:
            self.kind = emp_kind
            self.hours_worked = 0
            self.hire_cost = self.hc[self.kind]
            self.hours = self.hrs[self.kind]
            self.rate = self.rt[self.kind]
            self.overtime = self.ot[self.kind]
            self.benifits = self.ben[self.kind]

    def avail_hours(self):
        """
        Returns a tuple of the hours that the employee has left to work 
        (reg, overtime).
        """
        if OVERTIME - self.hours_worked >= 0:
            reg_hours = OVERTIME - self.hours_worked
            ot_hours = self.hours - OVERTIME
        else:
            reg_hours = 0
            ot_hours = self.hours - self.hours_worked
        if reg_hours < 0:
            reg_hours = 0
        if ot_hours < 0:
            ot_hours = 0
        return (reg_hours, ot_hours)

    def __str__(self):
        """
        Returns a string representing a snapshot of the employee's status.
        """
        return "%s -- %d hours, $%d.00/hr, %dxOT, $%d.00 in beinfits" % (self.kind, self.hours, self.rate, self.overtime, self.benifits)
# End of class Employee

class Problem_list(object):
    """
    Serves as the data structure that holds all of the problems and ensures that
    problems are taken out of circulation when completed and that the proper
    number of hours are deducted for each attempted problem.

    The problem list also maintains the active_problem which acts as an
    interface for the problem which is currently being solved.
    """
    global MAX_ATTEMPTS

    def __init__(self, backing_file):
        """
        Creates a list of problems by scanning a backing file.  There are two
        lists that problems can be in, p_list (for problems not yet attempted)
        and a_list for completed problems.  In between a single problem may be
        stored in the active_problem spot.
        """
        self.backing_file = backing_file
        self.p_list = list()
        self.a_list = list()
        self.active_problem = None
        self.company = None
        self.attempt_count = 0
        self.p_list = scan(self.backing_file)

    def __quick_check(self, p):
        """
        Method that is used by append to make sure that problems that are added
        fail on load and do not present a time-bomb to crash the system later.
        This is essential because we are reading in problems from a text file.
        
        In the future this should be moved to the problem class so that we can
        catch failures sooner, and be more flexible with the types of checks
        made on subclasses of Problem.
        """
        is_good = True
        if type(p.title) != str:
            is_good = False
        if type(p.cost) != int:
            is_good = False
        if type(p.payoff) != int:
            is_good = False
        if type(p.desc) != str:
            is_good = False
        if type(p.solution_format) != str:
            is_good = False
        if p.solution is None:
            is_good = False
        else:
            if p.solution_format == "int":
                if type(p.solution) != int:
                    is_good = False
            elif p.solution_format == "float":
                if type(p.solution) != float:
                    is_good = False
            elif p.solution_format == "string":
                if type(p.solution) != str:
                    is_good = False
            else:
                is_good = False
        if not is_good:
            print "ERROR: problem did not pass quick_check"
        return is_good

    def append(self, p):
        """
        Performs a quick check to make sure that the problem is as vaild as
        possible, and adds it to the list of problems.  We try to avoid
        duplicates by making sure that the title of the problem does not match
        the title of an existing problem.
        """
        if self.__quick_check(p):
            for op in self.p_list:
                if p.title == op.title:
                    return "ERROR: problem not added -- existing title = \"%s\"" % p.title
            self.p_list.append(p)

    def list_all(self):
        """
        Returns the intro for every problem in the p_list.  
        """
        ptl = list()
        if len(self.p_list):
            for i in range(0, len(self.p_list)):
                ptl.append("%d\t%s" % (i+1, self.p_list[i].intro()))
            return "\n".join(ptl)
        else:
            return "ERROR: empty problem list"

    def attempt(self, i):
        """
        First this makes sure that you have enough hours to work the problem,
        and if that passes we make sure that you are not already working a
        problem (FSM failure).  Then the problem is popped from p_list and
        placed in active_problem.
        """
        h = self.company.avail_hours()
        if self.active_problem is not None:
            return "ERROR: already working on a problem"
        elif i-1 not in range(0, len(self.p_list)):
            return "ERROR: %d is out of the range of problems" % i
        elif self.p_list[i-1].cost > h[0] + h[1]:
            return "ERROR: Not enough hours to attempt this problem.  Maybe it is time for a weekend?\n"
        else:
            self.active_problem = self.p_list.pop(i-1)
            return self.active_problem.display()

    def answer(self, a):
        """
        Keeps track of the number of failed attempts to answer a problem and
        checks submissions to problems.
        """
        self.attempt_count += 1
        h = self.company.avail_hours()
        if self.active_problem.cost <= h[0] + h[1]:
            result = self.active_problem.answer(a)
            if type(result) != str and result:
                self.company.hours += self.company.subtract_hours(self.active_problem.cost)
                self.company.capital += self.active_problem.payoff
                self.a_list.append(self.active_problem)
                self.active_problem = None
                self.attempt_count = 0
            elif self.attempt_count >= MAX_ATTEMPTS:
                self.company.hours += self.company.subtract_hours(self.active_problem.cost)
                self.a_list.append(self.active_problem)
                self.active_problem = None
                self.attempt_count = 0
                if self.company.hours >= self.company.work_week:
                    return "MAX ATTEMPTS Exceeded\n%s" % self.company.run_expenses()
                else:
                    return "MAX ATTEMPTS Exceeded"
            else:
                return result
        else:
            return "Not enough hours to attempt this problem.  Maybe it is time for a weekend?\n"
        if self.company.hours >= self.company.work_week:
            self.company.run_expenses()
            return result
        else:
            return result
    
    def abort(self):
        """
        Because the Problem_list is state-full this is required to maintain
        harmony in the case that the user calls an abort in the main part of the
        game.
        """
        self.attempt_count = 0
        if self.active_problem:
            self.a_list.append(self.active_problem)
            self.active_problem = None

    def size(self):
        """
        Returns the lenght of the p_list.
        """
        return len(self.p_list)
# End of class Problem_list

class Problem(object):
    """
    A data structure for holding problem details.  There are also methods for
    displaying a short intorduction of the problem and for displaying detailed
    information.  The answer method allows for the problem to check on its own
    to see if the provided answer matches the known solution.  The nice thing
    about doing this here is that it allows for easy additions of new types of
    problems with more complex answers by extending Problem.
    """

    def __init__(self, title, cost, payoff, desc, solution_format, solution):
        """
        Constructor that takes in the data that constitutes a problem.  In the
        future the __quick_check will be moved from Problem_list to here.
        """
        self.title = title
        self.cost = cost
        self.payoff = payoff #value
        self.desc = desc
        self.solution_format = solution_format
        self.solution = solution

    def intro(self):
        """
        Returns a one line summary string of a problem for list displays.
        """
        return "-%d hrs.\t+$%d.00\t| %s" % (self.cost, self.payoff,
                self.title)

    def display(self):
        """
        Returns a multi-line string that provides the full problem text.
        """
        return "%s\nCOST: %d hrs.\nRETURN: $%d.00\n----------------------------------\n%s" % (self.title, self.cost, self.payoff, self.desc)

    def answer(self, a):
        """
        Method that checks a potential answer.  In the future we should look at
        the way we are handling bad input as the return types from this method
        can be confusing.
        """
        if self.solution_format == "int":
            try:
                ans = int(a)
            except:
                return "ERROR: please enter an integer"
        elif self.solution_format == "float":
            try:
                ans = float(a)
            except:
                return "ERROR: please enter a decimal number"
        elif self.solution_format == "string":
            ans = a
        else:
            ans = a

        return self.solution == ans
    
    def __str__(self):
        """
        Wrapper that provides the standard sting interface by sending back the
        intro.
        """
        return self.intro()
# End of class Problem

class Game(object):
    """
    This is the little engiene that could.  This class is the highest level of
    the game that is specific to FirstHack.  It controls the rules and the flow
    of the game.  This is probably the most complex part of the code and most
    likely to break.  Changes here should be made with care.
    """
    global PROBLEM_LIST

    ei = Employee(Employee.i)
    eh = Employee(Employee.h)
    es = Employee(Employee.s)
    ec = Employee(Employee.c)
    eo = Employee(Employee.o)

    welcome = "What is your company's name?\n"
    home_state = "action"
    root_prompt = "(a)ttempt problem, (h)ire employee, (f)ire employee, (m)ove office, (w)eekend, or (q)uit"

    def __init__(self, name):
        """
        Primary constructor for a instance of the game.  Takes a string for the
        name of the company as an argument and creates a company with that name.
        Also creates a problem list with the backing file pointed to by the
        global PROBLEM_LIST.
        """
        self.name = name
        self.feedback = "Your Comapany name is \"%s\".\nLet's get going!\nWe have a lot of work to do...\n" % name
        self.prompt = self.root_prompt
        self.company0 = Company(name)
        self.problem_list = Problem_list(PROBLEM_LIST)
        self.problem_list.company = self.company0
        self.state = self.home_state
        self.status = self.company0.__str__()

    def abort(self):
        """
        Allows the user to go back to the home_state and resets things cleanly.
        """
        self.state = self.home_state
        self.feedback = "Aborting..."
        self.prompt = self.root_prompt
        self.problem_list.abort()

    def play(self, cmd):
        """
        This is the primary state machine that drives the game.  Handle with
        care.
        """
        if self.state == self.home_state:
            if cmd == "a":
                if self.problem_list.size():
                    self.state = "problem"
                    self.feedback = self.problem_list.list_all()
                    self.prompt = "Select a problem by number or \'a\' to abort:"
                else:
                    self.state = self.home_state
                    self.feedback = "Sorry there are no problems to be worked..."
            elif cmd == "h":
                self.state = "hire"
                self.feedback = "Let's bring in new talent!"
                self.prompt = "hire an (i)ntern, (h)ourly, (s)alary, or (c)ontract?"
            elif cmd == "f":
                self.state = "fire"
                self.feedback = "Time to take out the trash..."
                self.prompt = "fire an (i)ntern, (h)ourly, (s)alary, or (c)ontract?"
            elif cmd == "m":
                self.state = "move"
                self.feedback = "Renting a new pad"
                self.prompt = "Do you want a (p)ark, (d)owntown office, (s)ky scraper, (b)asement, or (c)ampus?"
            elif cmd == "w":
                reply = self.company0.run_expenses()
                if reply:
                    self.feedback = "Enjoy the weekend!\n%s" % reply
                else:
                    self.feedback = "Enjoy the weekend!"
                self.state = self.home_state
            else:
                self.feedback = "What? Try again..."
                return
        elif self.state == "problem":
            if cmd == "a":
                self.abort()
                return
            try:
                number = int(cmd)
            except:
                self.feedback = "%s\n\nWhat kind of number is \'%s\'? Try again..." % (self.problem_list.list_all(), cmd)
                return
            reply = self.problem_list.attempt(number)
            if reply.startswith("ERROR"):
                self.feedback = "%s\n\n%s" % (self.problem_list.list_all(), reply)
                return
            else:
                self.feedback = reply
                self.prompt = "Please enter your answer or \'a\' to abort:"
                self.state = "answer"
        elif self.state == "answer":
            if cmd == "a":
                self.abort()
                return
            reply = self.problem_list.answer(cmd)
            if type(reply) != str and reply:
                self.feedback = "Correct!"
                self.state = self.home_state
            elif type(reply) == str and reply.startswith("MAX"):
                self.feedback = reply
                self.state = self.home_state
            else:
                self.feedback = "%s\n\n%s -- Try agin..." % (self.problem_list.active_problem.display(), reply)
                return
        elif self.state == "hire":
            if cmd == "i":
                e = self.ei
            elif cmd == "h":
                e = self.eh
            elif cmd == "s":
                e = self.es
            elif cmd == "c":
                e = self.ec
            elif cmd == "o":
                e = self.eo
            else:
                self.feedback = "What? Try again..."
                return
            error = self.company0.hire_employee(Employee(e.kind))
            if error:
                self.feedback = error
                self.state = self.home_state
            else:
                self.feedback = "This guy will cost you $%d.00 and %d.00 per hour with a maximum of %d hours" % (e.hire_cost, e.rate, e.hours)
                self.state = self.home_state
        elif self.state == "fire":
            if cmd == "i":
                e = self.ei
            elif cmd == "h":
                e = self.eh
            elif cmd == "s":
                e = self.es
            elif cmd == "c":
                e = self.ec
            elif cmd == "o":
                e = self.eo
            else:
                self.feedback = "What? Try again..."
                return
            payout = self.company0.fire_employee(e.kind)
            if payout >= 0:
                self.feedback = "Payout for the %s that was fired was $%d.00" % (e.kind, payout)
                self.state = self.home_state
            else:
                self.feedback = "Couldn't find an employee on record of type %s" % e.kind
                self.state = self.home_state
        elif self.state == "move":
            if cmd == "p":
                self.feedback = self.company0.move_office(Office.p)
                self.state = self.home_state
            elif cmd == "d":
                self.beedback = self.company0.move_office(Office.d)
                self.state = self.home_state
            elif cmd == "s":
                self.feedback = self.company0.move_office(Office.s)
                self.state = self.home_state
            elif cmd == "b":
                self.feedback = self.company0.move_office(Office.b)
                self.state = self.home_state
            elif cmd == "c":
                self.feedback = self.company0.move_office(Office.c)
                self.state = self.home_state
            else:
                self.feedback = "What? Try again..."
                return

        if self.state == self.home_state:
            self.prompt = self.root_prompt
        self.status = self.company0.__str__()
        if self.company0.feedback is not None:
            self.feedback = "%s\n%s" % (self.feedback, self.company0.get_feedback())
# End of class Game

def scan(pfile):
    """
    This method is used to scan in a text file containing problem definitions.
    The structure of the file is as follows:

    #title - str
    #cost - int
    #value - int
    #desc - str
    #solution_format - str
    #solution - ?
    ########################
    World's Hardest Easy Geometry Problem
    1000
    2000
    Using only elementary geometry, determine angle x.
    int
    30

    Note that there is very little markup in the file other than the fact that
    lines starting with a # are interpred as comments.  This means that the
    sturcture of the file is key to it being parsed correctly.
    """
    f = open(pfile, "r")
    pl = list()
    lines = list()
    for l in f:
        if not l.startswith("#"):
            lines.append(l.strip())
    f.close()
    while len(lines):
        title = lines.pop(0)
        cost = int(lines.pop(0))
        payoff = int(lines.pop(0))
        desc = lines.pop(0)
        solution_format = lines.pop(0)
        solution = None
        if solution_format == "int":
            solution = int(lines.pop(0))
        elif solution_format == "float":
            solution = float(lines.pop(0))
        else:
            solution = lines.pop(0)

        p = Problem(title, cost, payoff, desc, solution_format, solution)
        pl.append(p)

    return pl

if __name__ == "__main__":
    if len(sys.argv) == 2:
        if sys.argv[1] == "-t":
            myapp = GameText()
            myapp.play()
        
    else:
        app = QtGui.QApplication(sys.argv)
        myapp = GameWindow()
        myapp.show()
        sys.exit(app.exec_())
